"""
有两种方法可以使对象动起来，一种是单击一个对象然后在节点编辑器中并选择“Animate”选项，
或者使用控制台中的‘app.animate’函数。

举一个简单的例子，下面的代码创建一个变量“ x”。
并对其值从0到1进行动画处理，延迟时间为0.1秒，步长0.05

>>> x=0
>>> app.animate("x", start=0, stop=1, delay=0.1, step=0.05)

app.animate函数的有用之处在于它是完全融合了绘制特征，
并且通过尽可能多的将动画呈现在绘制窗口来优化动画效果。


app.animate函数如此有用的另一个原因是，它可以
将动画包含在你的项目文档的代码示例中。

最有趣的动画之一是把幂函数应用到复杂平面中的一组网格线中。 让我们一探究竟！

>>> from examples import complex_grid
>>> from numpy import power
>>> t=1
>>> plot(power(complex_grid(), t))

绘制窗口打开后，使用以下命令运行动画

>>> app.animate("t", start=1, stop=4, step=0.005, delay=0.01)

你可以在播放动画时进行平移和缩放，只要鼠标光标在绘图窗口内。
通常动画应该超过60fps。如果在动画过程中鼠标光标没有悬停在绘图窗口上方时，
帧速可能会下降。 这是因为此时绘图窗口需要与其余窗口共享资源。

也可以为函数的参数设置动画

>>> from numpy import linspace
>>> app.animate("linspace", 参数名="start", start=0, stop=5)

关闭绘图窗口时，所有动画都会自动暂停。
要暂停或取消暂停当前正在运行的所有动画，
使用命令

>>> app.animate.pause()

"""
"""
Copyright (C) 2021 HBUT-RD

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""
import numpy as np
import plot
from widget.popup import Popup

DELAY = 0.01
STEP = 0.005

def hexstring(num):
    return '#' + str(hex(num))[2:].zfill(6)

class Animation:
	def __init__(self, app):
		self.app = app
		self.is_running = False
		self.stack = []
		self.cache = {}

	def __call__(self, name, **kwargs):
		item = self.app.node[name]

		val = 0 if item.is_callable else item.obj
		opts = {
			"起始值": val,
			"最终值": val + 10,
			"延迟时间": DELAY,
			"步长": STEP,
		}

		if kwargs:
			opts.update(kwargs)
			self.start(name, opts)
		else:
			if item.is_callable:
				opts.update({
					"参数名": ""
				})

			Popup(self.app, [{
				"label": i,
				"value": opts[i]	
			} for i in opts], 
			lambda params: self.start(name, params), 
			"Animate Item", name)

	def start(self, name, params):
		item = self.app.node[name]
		if params["最终值"] is None:
			print (Exception("AnimationError: Missing required parameters"))
			return

		if "参数名" in params and params["参数名"] is None:
			print (Exception("AnimationError: Missing required parameters"))
			return

		self.is_running = True
		if params["起始值"] is None:
			pass
		elif "参数名" in params:
			if params["参数名"] in item.args:
				item.args[params["参数名"]] = params["起始值"]
			elif params["参数名"] in item.kwargs:
				item.kwargs[params["参数名"]] = params["起始值"]
			else:
				print (Exception("AnimationError: 参数名 not found"))
				return
		else:
			self.app.objects[item.name] = params["起始值"]
		
		if self.app.node.output.items:
			plot.animate(params["延迟时间"], lambda: self._plot_update(name, params))
		else:
			self.run(item.name, params)

	def run(self, name, params):
		item = self.app.node[name]
		if "参数名" in params:
			if params["参数名"] in item.args:
				obj = self.app.node[name].args[params["参数名"]]
			else:
				obj = self.app.node[name].kwargs[params["参数名"]]
				return
		else:
			obj = self.app.objects[name]
		
		if self.is_running and (
			(params["步长"] > 0 and obj < params["最终值"]) 
			or (params["步长"] < 0 and obj > params["最终值"])
		):
			if "参数名" in params:
				if params["参数名"] in item.args:
					item.args[params["参数名"]] += params["步长"]
				else:
					item.kwargs[params["参数名"]] += params["步长"]
			else:
				self.app.objects[name] += params["步长"]
			self.app.after(int(params["延迟时间"]*1000), lambda: self.run(name, params))
			return
		
		if not self.is_running: 
			self.stack.append((name, params))
			return

		self.is_running = False
		if "参数名" in params:
			if params["参数名"] in item.args:
				item.args[params["参数名"]] = params["最终值"]
			else:
				item.kwargs[params["参数名"]] = params["最终值"]
		else:
			self.app.objects[name] = params["最终值"]

	def _plot_update(self, name, params):
		item = self.app.node[name]
		if "参数名" in params:
			if params["参数名"] in item.args:
				obj = item.args[params["参数名"]]
			else:
				obj = item.kwargs[params["参数名"]]
				return
		else:
			obj = self.app.objects[name]

		if self.is_running and (
			(params["步长"] > 0 and obj < params["最终值"]) 
			or (params["步长"] < 0 and obj > params["最终值"])
		):
			if "参数名" in params:
				if params["参数名"] in item.args:
					item.args[params["参数名"]] += params["步长"]
				else:
					item.kwargs[params["参数名"]] += params["步长"]
			else:
				self.app.objects[name] += params["步长"]

			self.app.update()
			return list(self.app.node.output.values.values())

		if "参数名" in params:
			if params["参数名"] in item.args:
				item.args[params["参数名"]] = params["最终值"]
			else:
				item.kwargs[params["参数名"]] = params["最终值"]
		else:
			self.app.objects[name] = params["最终值"]
		self.app.update()
		return False

	def can_animate(self, item):
		return item.is_callable or isinstance(item.obj, (int,float))

	def pause(self):
		if self.is_running:
			self.is_running = False
		else:
			for i in self.stack:
				i[1]["起始值"] = None
				self.start(i[0], i[1])
			self.stack.clear()
